# -*- coding: utf-8 -*-
"""
报表定时任务调度器
负责将报表配置中启用的定时任务注册到APScheduler
"""
import logging
from typing import List, Dict, Any
from datetime import datetime, timedelta, timezone

from app.services.report_service import ReportConfigService, ReportOrchestrator
from app.blueprints.reports.providers import get_provider
from app.blueprints.reports.renderers import get_renderer
from app.services.notification_service import NotificationService
from apscheduler.triggers.cron import CronTrigger

logger = logging.getLogger(__name__)


class ReportScheduler:
    """报表定时任务调度器"""
    
    def __init__(self, scheduler):
        """
        初始化调度器
        :param scheduler: APScheduler实例
        """
        self.scheduler = scheduler
        self.job_prefix = 'report_'
    
    def sync_all_configs(self):
        """
        同步所有报表配置到调度器
        """
        logger.info("开始同步报表定时任务配置...")
        
        try:
            # 获取所有启用定时的配置
            configs = ReportConfigService.get_all_configs(is_active=True)
            enabled_configs = [c for c in configs if c.get('schedule_enabled')]
            
            # 获取当前所有报表任务
            existing_jobs = self._get_report_jobs()
            existing_job_ids = {job.id for job in existing_jobs}
            
            # 配置的任务ID集合
            config_job_ids = set()
            
            # 添加或更新任务
            for config in enabled_configs:
                job_id = self._get_job_id(config['id'])
                config_job_ids.add(job_id)
                
                if job_id in existing_job_ids:
                    # 更新现有任务
                    self._update_job(config)
                else:
                    # 添加新任务
                    self._add_job(config)
            
            # 删除已移除的任务
            for job_id in existing_job_ids - config_job_ids:
                self._remove_job(job_id)
            
            logger.info(f"报表定时任务同步完成，当前活跃任务数：{len(config_job_ids)}")
            
        except Exception as e:
            logger.error(f"同步报表定时任务失败：{str(e)}")
    
    def add_config_job(self, config_id: int):
        """
        添加单个配置的定时任务
        :param config_id: 配置ID
        """
        try:
            config = ReportConfigService.get_config_by_id(config_id)
            
            if not config:
                logger.warning(f"配置 {config_id} 不存在")
                return
            
            if not config.get('is_active') or not config.get('schedule_enabled'):
                logger.info(f"配置 {config_id} 未启用或未启用定时")
                return
            
            self._add_job(config)
            
        except Exception as e:
            logger.error(f"添加配置 {config_id} 的定时任务失败：{str(e)}")
    
    def remove_config_job(self, config_id: int):
        """
        移除单个配置的定时任务
        :param config_id: 配置ID
        """
        job_id = self._get_job_id(config_id)
        self._remove_job(job_id)
    
    def update_config_job(self, config_id: int):
        """
        更新单个配置的定时任务
        :param config_id: 配置ID
        """
        try:
            config = ReportConfigService.get_config_by_id(config_id)
            
            if not config:
                # 配置不存在，移除任务
                self.remove_config_job(config_id)
                return
            
            if not config.get('is_active') or not config.get('schedule_enabled'):
                # 配置未启用或未启用定时，移除任务
                self.remove_config_job(config_id)
                return
            
            # 更新任务
            self._update_job(config)
            
        except Exception as e:
            logger.error(f"更新配置 {config_id} 的定时任务失败：{str(e)}")
    
    def _get_job_id(self, config_id: int) -> str:
        """获取任务ID"""
        return f"{self.job_prefix}{config_id}"
    
    def _get_report_jobs(self) -> List:
        """获取所有报表任务"""
        jobs = self.scheduler.get_jobs()
        return [job for job in jobs if job.id.startswith(self.job_prefix)]
    
    def _add_job(self, config: Dict[str, Any]):
        """添加定时任务"""
        try:
            job_id = self._get_job_id(config['id'])
            cron_expr = config['schedule_cron']
            
            # 校验并构建 CronTrigger（支持5/6段）
            trigger = self._build_cron_trigger(cron_expr, config['id'])
            if not trigger:
                logger.error(f"构建触发器失败，无法添加任务：{job_id}")
                return
            
            # 添加任务（APScheduler 允许在启动前添加任务，任务会在调度器启动后自动执行）
            self.scheduler.add_job(
                func=self._execute_report,
                trigger=trigger,
                id=job_id,
                name=f"报表：{config['config_name']}",
                args=[config['id']],
                replace_existing=False
            )
            
            # 验证任务是否添加成功
            try:
                job = self.scheduler.get_job(job_id)
                if job:
                    # 安全地获取 next_run_time（某些情况下可能不存在）
                    next_run_time = getattr(job, 'next_run_time', None)
                    next_run = next_run_time.strftime('%Y-%m-%d %H:%M:%S') if next_run_time else 'N/A'
                    scheduler_status = "运行中" if self.scheduler.running else "未启动（将在启动后执行）"
                    logger.info(f"已添加报表定时任务：{job_id} ({config['config_name']}) - {cron_expr}，下次执行时间：{next_run}，调度器状态：{scheduler_status}")
                else:
                    logger.error(f"任务添加失败，任务未找到：{job_id}")
            except Exception as e:
                logger.error(f"验证任务失败：{str(e)}")
                logger.info(f"已添加报表定时任务：{job_id} ({config['config_name']}) - {cron_expr}")
            
        except Exception as e:
            logger.error(f"添加任务失败：{str(e)}", exc_info=True)
    
    def _update_job(self, config: Dict[str, Any]):
        """更新定时任务"""
        try:
            job_id = self._get_job_id(config['id'])
            cron_expr = config['schedule_cron']
            
            trigger = self._build_cron_trigger(cron_expr, config['id'])
            if not trigger:
                logger.error(f"构建触发器失败，无法更新任务：{job_id}")
                return
            
            # 先移除旧任务（如果存在）
            self._remove_job(job_id)
            
            # 添加新任务（APScheduler 允许在启动前添加任务，任务会在调度器启动后自动执行）
            self.scheduler.add_job(
                func=self._execute_report,
                trigger=trigger,
                id=job_id,
                name=f"报表：{config['config_name']}",
                args=[config['id']],
                replace_existing=True
            )
            
            # 验证任务是否添加成功
            try:
                job = self.scheduler.get_job(job_id)
                if job:
                    # 安全地获取 next_run_time（某些情况下可能不存在）
                    next_run_time = getattr(job, 'next_run_time', None)
                    next_run = next_run_time.strftime('%Y-%m-%d %H:%M:%S') if next_run_time else 'N/A'
                    scheduler_status = "运行中" if self.scheduler.running else "未启动（将在启动后执行）"
                    logger.info(f"已更新报表定时任务：{job_id} ({config['config_name']}) - {cron_expr}，下次执行时间：{next_run}，调度器状态：{scheduler_status}")
                else:
                    logger.error(f"任务更新失败，任务未找到：{job_id}")
            except Exception as e:
                logger.error(f"验证任务失败：{str(e)}")
                logger.info(f"已更新报表定时任务：{job_id} ({config['config_name']}) - {cron_expr}")
            
        except Exception as e:
            logger.error(f"更新任务失败：{str(e)}", exc_info=True)
    
    def _build_cron_trigger(self, expr: str, config_id: int):
        """构建 CronTrigger，支持5/6段，并记录错误到日志"""
        try:
            parts = (expr or '').split()
            if len(parts) == 6:
                second, minute, hour, day, month, dow = parts
                return CronTrigger(second=second, minute=minute, hour=hour, day=day, month=month, day_of_week=dow, timezone='Asia/Shanghai')
            if len(parts) == 5:
                minute, hour, day, month, dow = parts
                return CronTrigger(minute=minute, hour=hour, day=day, month=month, day_of_week=dow, timezone='Asia/Shanghai')
            logger.error(f"配置 {config_id} 的cron表达式格式错误（需5或6段）：{expr}")
            return None
        except Exception as e:
            logger.error(f"配置 {config_id} 的cron表达式解析失败：{expr}，错误：{str(e)}")
            return None
    
    def _remove_job(self, job_id: str):
        """移除定时任务"""
        try:
            self.scheduler.remove_job(job_id)
            logger.info(f"已移除报表定时任务：{job_id}")
        except Exception as e:
            # 任务可能不存在
            logger.debug(f"移除任务 {job_id} 失败（可能不存在）：{str(e)}")
    
    @staticmethod
    def _execute_report(config_id: int):
        """
        执行报表生成任务
        :param config_id: 配置ID
        """
        import datetime
        now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=8)))
        logger.info(f"🕐 [{now.strftime('%Y-%m-%d %H:%M:%S')}] 开始执行报表定时任务，配置ID：{config_id}")
        
        try:
            # 获取配置
            config = ReportConfigService.get_config_by_id(config_id)
            
            if not config:
                logger.error(f"配置 {config_id} 不存在")
                return
            
            import json
            
            # 解析参数
            params = json.loads(config['params']) if isinstance(config['params'], str) else config['params']
            output_formats = json.loads(config['output_formats']) if isinstance(config['output_formats'], str) else config['output_formats']
            
            # 从参数中提取标题（如果存在），如果为空则使用config_name作为title
            report_title = params.get('title') if isinstance(params, dict) else None
            if not report_title:
                report_title = config.get('config_name')
            
            # 创建编排器
            orchestrator = ReportOrchestrator()
            
            # 创建运行记录
            run_id = orchestrator.create_run(
                template_code=config['template_code'],
                params=params,
                config_id=config_id,
                trigger_type='scheduled',
                title=report_title,
                output_formats=output_formats  # 传递输出格式
            )
            
            logger.info(f"已创建运行记录，run_id：{run_id}")
            
            # 获取数据提供者
            provider = get_provider(config['template_code'], params)
            
            # 获取运行记录以获取标题和时间范围
            from app.services.report_service import ReportRunService
            run_record = ReportRunService.get_run_by_id(run_id)
            actual_title = run_record.get('title') if run_record else report_title
            time_start = run_record.get('time_start') if run_record else None
            time_end = run_record.get('time_end') if run_record else None
            
            # 生成各种格式的报表
            for file_type in output_formats:
                renderer = get_renderer(file_type, config['template_code'], title=actual_title, time_start=time_start, time_end=time_end)
                orchestrator.generate_report(run_id, provider, renderer)
            
            # 发送通知（如果配置了）
            if config.get('notify_on_success'):
                NotificationService.send_report_notification(run_id)
            
            logger.info(f"报表定时任务执行成功，run_id：{run_id}")
            
        except Exception as e:
            logger.error(f"执行报表定时任务失败：{str(e)}", exc_info=True)


def init_report_scheduler(scheduler):
    """
    初始化报表调度器
    :param scheduler: APScheduler实例
    :return: ReportScheduler实例
    """
    report_scheduler = ReportScheduler(scheduler)
    
    # 同步所有配置
    report_scheduler.sync_all_configs()
    
    return report_scheduler


# 全局调度器实例
_report_scheduler = None


def get_report_scheduler():
    """获取全局报表调度器实例"""
    return _report_scheduler


def set_report_scheduler(scheduler):
    """设置全局报表调度器实例"""
    global _report_scheduler
    _report_scheduler = scheduler








